Tiny FORTRAN FORM ――――――――――――――――――――――――――――――――――――――― I/O 1980年 5、6、7、8、9月号掲載 MZ−80 マシン語 起動方法 モニタからロード ――――――――――――――――――――――――――――――――――――――― [3]コンパイラについて ――――――――――――――――――― 1 FORMのコンパイラ ――――――――――――――――――― インタープリタは皆さんもよく知っているとおり ソース・プログラムを読みながら解釈し、 そのつど実行するものです。 したがって、ソース・プログラムを実行させるためには インタープリタが実行時に存在していなければなりません。 この結果メモリの使用効率は極めて悪く、実行スピードが遅くなります。 その反面、インタープリタ自体の構造も比較的簡単に作ることができ、 また、大容量の外部ファイルやOSも必要なく、 小規模システムより乗り易いという特徴があります。 これと反対に、コンパイラはソース・プログラムを一括して マシン語のプログラムに変換してしまうので、 実行時にはコンパイラそのものが必要なく、 コンパイルされたオブジェクト・プログラムがあれば実行可能です。 実行速度はマシン語レベルのスピードを維持できますが、 反対にコンパイルするために時間がかかり、 コンパイル時のメモリ使用効率は悪くなります。 また、大容量の外部ファイルが必要となり、 システムがある程度大きくないと、コンパイラを走らせることができません。 FORMは一応コンパイラに属しますが、 外部ファイルを使用せず、 すべてメモリの中で処理するという 通常のコンパイラから見れば特異な構造を持っています。 しかし、このような構造にすることで、 すぐ実行可能なオブジェクトを生成することができ、 インタープリタ並みにプログラムを開発するということが ある程度できるようになっています。 また、サブルーチンのリンクに関することですが、 以前にも述べた通り固定リンクの方法を使っています。 通常、 コンパイラはテキストをコンパイルして リロケータブル・バイナリ(RB)の形で出力します。 次に、このRBをリロケータブル・ローダ またはリンカーを使ってサブプログラムのリンクを行ない、 実行イメージ・オブジェクトを作り、 それを一旦外部ファイルに出力し、 そのファイルを通常の実行形バイナリとして実行します。 このような形態の場合、必ず外部ファイルを必要とします。 しかもリンカーなどが必要な上、 コンパイルするためにかなり時間がかかります。 FORMは固定リンクの採用により、 リンカーやローダが不必要になりました。 このことは、コンパイル時にすでにリンクしたパッケージを 用意することで実現できます。 このことから、当然FORMのオブジェクトは リロケータブルなものではなく、 固定のアドレスでしか動作しません。 固定リンクは複雑なリンカーが必要でなく、 すぐ実行可能なオブジェクトが作れるため、 コンパイラそのものが簡単になるという特徴を持っています。 ―――――――――――――――――――  2 FORMの構造 ――――――――――――――――――― FORMは基本的にRAM20Kで動作するように設計され、 大別してエディタとコンパイラに分かれます。 エディタはそう重要ではないので、ここでは省略します。 興味のある方はシャープのテキスト・エディタを参考にしてください。 1)式について 式はすべて整数演算で、変数、定数、配列と演算子の加減乗除、 および関数から構成されています。 変数は最初の1文字が英字で始まる英数字4文字以内で表わし、 定数は10進および16進定数があります。 関数はGET、ABS、MOD、LOW、 SIGN、IOC、MEM、RNDの8個です。 GETはリアルタイムのキー入力、 IOCはI/Oポートを直接アクセスし、 MEMはメモリ内を直接アクセスできる関数です。 さて、この式のコンパイルは図1のような構文に基づいて作られます。 実際に定数は、 ・―――――――――――――――――・ |  LD HL、XXXXH    | ・―――――――――――――――――・ となり、変数は、 ・―――――――――――――――――・ |  LD HL、(XXXXH)  | ・―――――――――――――――――・ というように展開します。 配列はBC、DEに配列のパラメータを入れ、HLに引数を持たせて、 配列の処理サブルーチンをコールします。 ただし、1次元、2次元はパラメータの意味が少々異なります。 関数は各関数でそれぞれ違う処理をしているので、 パラメータをセットするレジスタは異なります。        図1 式の構文図  a)TERM         / ̄ ̄ ̄ ̄ ̄ ̄ ̄\           ○――――| T E R M |―――――○               ↑ \_______/ |               |           |               |   ・−−−・   |               | ・―| + |―・ |               |_| ・−−−・ |←・                 | ・−−−・ |                 ・―| − |―・                   ・−−−・  b)FACT         / ̄ ̄ ̄ ̄ ̄ ̄ ̄\           ○――――| F A C T |―――――○               ↑ \_______/ |               |           |               |   ・−−−・   |               | ・―| * |―・ |               |_| ・−−−・ |←・                 | ・−−−・ |                 ・―| / |―・                   ・−−−・  c)式              / ̄ ̄ ̄\           ○――――――| 定 数 |――――――○            |      \___/      ↑            |      / ̄ ̄ ̄\      |            |―――――| 変 数 |――――→|            |      \___/      |            |      / ̄ ̄ ̄\      |            |―――――| 関 数 |――――→|            |      \___/      |            | ・―・  / ̄ ̄ ̄\  ・―・ |            ・→|(|―|  式  |―|)|―・              ・―・  \___/  ・―・ RND、ABS、GET、MOD、SIGN、IOCの一部は サブルーチンで処理しますが、 IOCの一部やMEM、LOWは 展開したオブジェクトの方で処理していきます。 次に演算子ですが、 すべてDE、HL間で演算を行ないHLに結果を入れます。 2)基本的なコンパイルの動作 A*B+C*Dは次のように展開されます。 ・―――――――――――――――――・ |  LD HL,(VARA)        | |  PUSH DE            | |  EX DE,HL          | |  LD HL,(VARB)        | |  CALL MLTSUB          | |  POP DE     ̄|*     | |  PUSH DE    _|      | |  EX DE,HL          | |  LD HL,(VARC)        | |  PUSH DE            | |  EX DE,HL          | |  LD HL,(VARD)        | |  CALL MLTSUB          | |  POP DE            | |  ADD HL,DE          | |  POP DE            | ・―――――――――――――――――・ FORMではテキストは上記のようにコンパイルされますが、 一見して『*』のところがムダであることがわかると思います。 この部分はオプティマイズすべきなのですが、 この式を逆ポーランド記法を使って処理すれば解決するものの、 そこまでやってしまうとコンパイラそのものが複雑になります。 整数型でもあるし、無理してそこまでやる必要性も感じなかったので、 今回はそのままにしてあります。 3)変数の管瓊 次に変数の管理ですが、 FORMは1パス形のため以下の方法を採用しています。 コンパイル中、変数が出てきた場合は 変数テーブルをサーチして、 その変数がなければテーブルを追加して オフセット・アドレスを増じて使います。 そして、そのオフセットを一時的な変数のアドレスとして処理し、 そのアドレスをアクセスするアドレスをテーブルに定義します。 コンパイルが終了した段階では オブジェクトの終了アドレスにオフセットを加えたアドレスが 物理的な変数のアドレスになります。 具体的には、変数テーブルをサーチして、 その定義したアドレス内のデータを読み込み、 オブジェクトの終了アドレスを加算してからテーブルに戻しています。 したがって、変数を1回使用すると そのアドレスを表現するために3バイトのテーブルが使用されます。 また、1つの変数に付き8バイトの管理テーブルが使われます。 4)実際のコンパイル それでは、例として次の式をコンパイルしてみます。 ・――――――――――――――――――――――――・ | ABC+10*ZZZ−($20+JJJ/3) | ・――――――――――――――――――――――――・ まず、変数名をフェッチし、リザーブド・ワード(予約語)かどうか判断します。 リザーブのときはエラーとし、この行はコンパイルされません。 次に、関数かどうか判断します。 この場合、単なる変数なので関数処理は行ないません。 さて、ABCという変数はすでにテーブルに定義されているものとすると、 次のように展開されます。 ・―――――――――――――――――・ |  LD HL,(ABC)         | ・―――――――――――――――――・ このオペランドは前に述べたように、 オフセット・アドレスなのでテーブルを参照します。 次に演算子があるので、この演算子を判断し、各演算ルーチンヘ飛びます。 これの後に10進定数があります。 この定数をHLに入れなくてはなりませんが、 現在HLは使用中ですからHLをDEへ転送します。 DEの中を保護するために、いったんスタックヘ退避させます。 すると、次のように展開されたプログラムが得られます。 ・―――――――――――――――――・ |  LD HL,(ABC)         | |  PUSH DE            | |  EX DE,HL          | |  LD HL,0AH          | ・―――――――――――――――――・ 定数の後の演算子は『*』ですから、『+』より先に演算を行ないます。 したがって、再びHLに変数の値を持ってこなくてはなりませんが、 各レジスタは現在使用中なので、これを保護しなくてはなりません。 ・―――――――――――――――――・ |  PUSH DE            | |  EX DE,HL          | |  LD HL,(ZZZ)         | |  CALL MLTSUB          | ・―――――――――――――――――・ 『*』は上記のように展開され処理されます。 次の演算子は『−』ですが、『+』と同レベルなので、 『+』の処理を先に、行ないます。 ・―――――――――――――――――・ |  POP DE            | |  ADD HL,DE          | |  EX DE,HL          | ・―――――――――――――――――・ これで『+』の処理は終了します。 次の項は『(式)』なのでレベル上では1レベル先に実行しなければなりません。 これもまたDEレジスタを保護しなくてはなりません。 ・―――――――――――――――――・ |  LD HL,20H          | |  PUSH DE            | |  EX DE,HL          | ・―――――――――――――――――・ 次の『+』は、前にある『−』よりも1レベル高いことになっているので、 『+』の処理を先に行ないます。 そこで、まずHLに次の変数をもってきます。 ・―――――――――――――――――・ |  LD HL,(JJJ)         | ・―――――――――――――――――・ 次の演算子は『/』なので『+』より先に処理します。 ・―――――――――――――――――・ |  PUSH DE            | |  EX DE,HL          | |  LD HL,03H          | |  CALL DIVSUB          | |  POP DE            | ・―――――――――――――――――・ 次に『+』の処理を行ないます。 ・―――――――――――――――――・ |  ADD HL,DE          | |  POP DE            | ・―――――――――――――――――・ 最後に『−』の処理を行ない、一応この式のコンパイルは終ります。 ・―――――――――――――――――・ |  EX DE,HL          | |  OR A            | |  SBC HL,DE          | |  POP DE            | ・―――――――――――――――――・ 以上、HLに式の値がセットされ、DEは保護されています。 このように式が展開されていく様子が理解できると思います。 扱う数字が16bitなのでZ80で比較的簡単に展開できますが、 これが浮動少数点演算の場合、さらに複雑になります。 ――――――――――――――――――― 3 最後に ――――――――――――――――――― コンパイラを設計するにあたって 第1に問題となるのは、この式評価のところであると思います。 皆さんもどうやったら効率的に展開できるか考えてみてください。 最後に、FORMコンパイラのアセンブル・リストを公開します。 Eのでているところは、外部参照ファイルの印で、 これはリンク・パッケージの中を呼んでいるために出ています。